﻿using SIR.Color;
using SIR.Entities.Implementations;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace SIR.Services.ImageProcessing
{
	internal static class IdentifyPatterns
	{
		/// <summary>
		/// Consolidate all of the defined patterns to be run together
		/// </summary>
		static Dictionary<long, Func<long, long, List<PixelColor>, Task>> _knownPatterns =
			new Dictionary<long, Func<long, long, List<PixelColor>, Task>>()
            {
                { 1, Pattern_Black },
                { 2, Pattern_Red },
                { 3, Pattern_Magenta },
                { 4, Pattern_Blue },
                { 5, Pattern_Cyan },
                { 6, Pattern_Green },
                { 7, Pattern_Yellow },
                { 8, Pattern_White },
				{ 9, Pattern_BlackAndWhite },
				{ 10, Pattern_GrayScale },
				{ 11, Pattern_Sepia },
				{ 12, Pattern_Mountains },
				{ 13, Pattern_Beaches },
				{ 14, Pattern_Snow },
				{ 15, Pattern_Forest },
				{ 16, Pattern_Sunset },
            };

		/// <summary>
		/// Creates and saves the association between the image and the pattern
		/// </summary>
		/// <param name="imageId">The identifier for the image</param>
		/// <param name="patternDefinitionId">The identifier for the pattern</param>
		/// <param name="percentage">The percentage that the image matches the pattern</param>
		/// <returns>A DataServerResults object</returns>
		private static DAL.DataServerResult<ImagePattern> SaveImagePattern(long imageId, long patternDefinitionId, decimal percentage)
		{
			// Create the image pattern object with the passed values and save it to the database
			return DAL.DataServer.Save(new ImagePattern() { ImageId = imageId, PatternDescriptionId = patternDefinitionId, MatchPercentage = percentage });
		}

		/// <summary>
		/// Asynchronous method to check the image for all known patterns
		/// </summary>
		/// <param name="imageId">The identifier for the image being processed</param>
		/// <param name="pixelColorsMatrix">A matrix containing the pixels for the image</param>
		/// <returns>A task object that returns a void</returns>
		internal static async Task ForImage(long imageId, ListMatrix2<PixelColor> pixelColorsMatrix)
		{
			var tasks = new List<Task>();
			// Convert the matrix to a single list of pixels
			var pixelColors = pixelColorsMatrix.ToList();
			// Loop through all of the patterns
			foreach (var kvp in _knownPatterns)
			{
				// Task out the processing of all the patterns
				tasks.Add(kvp.Value(imageId, kvp.Key, pixelColors));
			}

			// Do not exit until all patterns have been evaluated
			await Task.WhenAll(tasks);
		}

		/// <summary>
		/// Returns true if the the value is between the two values
		/// </summary>
		/// <param name="value">The value to check</param>
		/// <param name="min">The lowerbounds of the check</param>
		/// <param name="max">The upperbounds of the check</param>
		/// <param name="inclusive">Whether or not the bounds should be included in the check ">=" or ">"</param>
		/// <returns>True if the value is between both values</returns>
		private static bool Between(this double value, double min, double max, bool inclusive = true)
		{
			return inclusive ? (value >= min && value <= max) :
				(value > min && value < max);
		}

		#region "Colors"

		/// <summary>
		/// Evaluate the pixels for the "Black" pattern
		/// </summary>
		/// <param name="imageId">The identifier for the image</param>
		/// <param name="patternDefinitionId">The identifier for the pattern</param>
		/// <param name="pixelColors">The list of pixels from the image</param>
		/// <returns>An asynchronous task to be completed on a different thread</returns>
		private static async Task Pattern_Black(long imageId, long patternDefinitionId, List<PixelColor> pixelColors)
		{
			await Task.Run(() =>
			{
				var matchCount = pixelColors.Count(c => { return c.Brightness <= 10 && c.Saturation <= 10; });
				decimal percentage = ((decimal)matchCount) / pixelColors.Count;
				if (percentage >= .5m)
				{
					SaveImagePattern(imageId, patternDefinitionId, percentage);
				}
			});
		}
		/// <summary>
		/// Evaluate the pixels for the "Red" pattern
		/// </summary>
		/// <param name="imageId">The identifier for the image</param>
		/// <param name="patternDefinitionId">The identifier for the pattern</param>
		/// <param name="pixelColors">The list of pixels from the image</param>
		/// <returns>An asynchronous task to be completed on a different thread</returns>
		private static async Task Pattern_Red(long imageId, long patternDefinitionId, List<PixelColor> pixelColors)
		{
			await Task.Run(() =>
			{
				var matchCount = pixelColors.Count(c => { return (c.Hue.Between(0, 30) || c.Hue.Between(330, 360)) && c.Saturation >= 40 && c.Brightness > 10; });
				decimal percentage = ((decimal)matchCount) / pixelColors.Count;
				if (percentage >= .5m)
				{
					SaveImagePattern(imageId, patternDefinitionId, percentage);
				}
			});
		}
		/// <summary>
		/// Evaluate the pixels for the "Magenta" pattern
		/// </summary>
		/// <param name="imageId">The identifier for the image</param>
		/// <param name="patternDefinitionId">The identifier for the pattern</param>
		/// <param name="pixelColors">The list of pixels from the image</param>
		/// <returns>An asynchronous task to be completed on a different thread</returns>
		private static async Task Pattern_Magenta(long imageId, long patternDefinitionId, List<PixelColor> pixelColors)
		{
			await Task.Run(() =>
			{
				var matchCount = pixelColors.Count(c => { return c.Hue.Between(270, 330) && c.Saturation >= 40 && c.Brightness > 10; });
				decimal percentage = ((decimal)matchCount) / pixelColors.Count;
				if (percentage >= .5m)
				{
					SaveImagePattern(imageId, patternDefinitionId, percentage);
				}
			});
		}
		/// <summary>
		/// Evaluate the pixels for the "Blue" pattern
		/// </summary>
		/// <param name="imageId">The identifier for the image</param>
		/// <param name="patternDefinitionId">The identifier for the pattern</param>
		/// <param name="pixelColors">The list of pixels from the image</param>
		/// <returns>An asynchronous task to be completed on a different thread</returns>
		private static async Task Pattern_Blue(long imageId, long patternDefinitionId, List<PixelColor> pixelColors)
		{
			await Task.Run(() =>
			{
				var matchCount = pixelColors.Count(c => { return c.Hue.Between(210, 270) && c.Saturation >= 40 && c.Brightness > 10; });
				decimal percentage = ((decimal)matchCount) / pixelColors.Count;
				if (percentage >= .5m)
				{
					SaveImagePattern(imageId, patternDefinitionId, percentage);
				}
			});
		}
		/// <summary>
		/// Evaluate the pixels for the "Cyan" pattern
		/// </summary>
		/// <param name="imageId">The identifier for the image</param>
		/// <param name="patternDefinitionId">The identifier for the pattern</param>
		/// <param name="pixelColors">The list of pixels from the image</param>
		/// <returns>An asynchronous task to be completed on a different thread</returns>
		private static async Task Pattern_Cyan(long imageId, long patternDefinitionId, List<PixelColor> pixelColors)
		{
			await Task.Run(() =>
			{
				var matchCount = pixelColors.Count(c => { return c.Hue.Between(150, 210) && c.Saturation >= 40 && c.Brightness > 10; });
				decimal percentage = ((decimal)matchCount) / pixelColors.Count;
				if (percentage >= .5m)
				{
					SaveImagePattern(imageId, patternDefinitionId, percentage);
				}
			});
		}
		/// <summary>
		/// Evaluate the pixels for the "Green" pattern
		/// </summary>
		/// <param name="imageId">The identifier for the image</param>
		/// <param name="patternDefinitionId">The identifier for the pattern</param>
		/// <param name="pixelColors">The list of pixels from the image</param>
		/// <returns>An asynchronous task to be completed on a different thread</returns>
		private static async Task Pattern_Green(long imageId, long patternDefinitionId, List<PixelColor> pixelColors)
		{
			await Task.Run(() =>
			{
				var matchCount = pixelColors.Count(c => { return c.Hue.Between(90, 150) && c.Saturation >= 40 && c.Brightness > 10; });
				decimal percentage = ((decimal)matchCount) / pixelColors.Count;
				if (percentage >= .5m)
				{
					SaveImagePattern(imageId, patternDefinitionId, percentage);
				}
			});
		}
		/// <summary>
		/// Evaluate the pixels for the "Yellow" pattern
		/// </summary>
		/// <param name="imageId">The identifier for the image</param>
		/// <param name="patternDefinitionId">The identifier for the pattern</param>
		/// <param name="pixelColors">The list of pixels from the image</param>
		/// <returns>An asynchronous task to be completed on a different thread</returns>
		private static async Task Pattern_Yellow(long imageId, long patternDefinitionId, List<PixelColor> pixelColors)
		{
			await Task.Run(() =>
			{
				var matchCount = pixelColors.Count(c => { return c.Hue.Between(30, 90) && c.Saturation >= 40 && c.Brightness > 10; });
				decimal percentage = ((decimal)matchCount) / pixelColors.Count;
				if (percentage >= .5m)
				{
					SaveImagePattern(imageId, patternDefinitionId, percentage);
				}
			});
		}
		/// <summary>
		/// Evaluate the pixels for the "White" pattern
		/// </summary>
		/// <param name="imageId">The identifier for the image</param>
		/// <param name="patternDefinitionId">The identifier for the pattern</param>
		/// <param name="pixelColors">The list of pixels from the image</param>
		/// <returns>An asynchronous task to be completed on a different thread</returns>
		private static async Task Pattern_White(long imageId, long patternDefinitionId, List<PixelColor> pixelColors)
		{
			await Task.Run(() =>
			{
				var matchCount = pixelColors.Count(c => { return c.Brightness >= 85 && c.Saturation <= 15; });
				decimal percentage = ((decimal)matchCount) / pixelColors.Count;
				if (percentage >= .7m)
				{
					SaveImagePattern(imageId, patternDefinitionId, percentage);
				}
			});
		}

		#endregion "Colors"

		#region "Themes"
		/// <summary>
		/// Evaluate the pixels for the "Black and White" pattern
		/// </summary>
		/// <param name="imageId">The identifier for the image</param>
		/// <param name="patternDefinitionId">The identifier for the pattern</param>
		/// <param name="pixelColors">The list of pixels from the image</param>
		/// <returns>An asynchronous task to be completed on a different thread</returns>
		private static async Task Pattern_BlackAndWhite(long imageId, long patternDefinitionId, List<PixelColor> pixelColors)
		{
			await Task.Run(() =>
			{
				var blackCount = pixelColors.Count(c => { return c.Saturation <= 10 && c.Brightness <= 10; });
				var whiteCount = pixelColors.Count(c => { return c.Saturation <= 10 && c.Brightness >= 90; });
				var count = pixelColors.Count;
				decimal b_Percent = ((decimal)blackCount) / count;
				decimal w_Percent = ((decimal)whiteCount) / count;

				if (b_Percent >= .1m && w_Percent >= .1m && (b_Percent + w_Percent) >= .7m)
				{
					SaveImagePattern(imageId, patternDefinitionId, b_Percent + w_Percent);
				}
			});
		}
		/// <summary>
		/// Evaluate the pixels for the "Gray Scale" pattern
		/// </summary>
		/// <param name="imageId">The identifier for the image</param>
		/// <param name="patternDefinitionId">The identifier for the pattern</param>
		/// <param name="pixelColors">The list of pixels from the image</param>
		/// <returns>An asynchronous task to be completed on a different thread</returns>
		private static async Task Pattern_GrayScale(long imageId, long patternDefinitionId, List<PixelColor> pixelColors)
		{
			await Task.Run(() =>
			{
				var matchCount = pixelColors.Count(c => { return c.Saturation <= 10; });
				decimal percentage = ((decimal)matchCount) / pixelColors.Count;

				if (percentage >= .7m)
				{
					SaveImagePattern(imageId, patternDefinitionId, percentage);
				}
			});
		}
		/// <summary>
		/// Evaluate the pixels for the "Sepia" pattern
		/// </summary>
		/// <param name="imageId">The identifier for the image</param>
		/// <param name="patternDefinitionId">The identifier for the pattern</param>
		/// <param name="pixelColors">The list of pixels from the image</param>
		/// <returns>An asynchronous task to be completed on a different thread</returns>
		private static async Task Pattern_Sepia(long imageId, long patternDefinitionId, List<PixelColor> pixelColors)
		{
			await Task.Run(() =>
			{
				var matchCount = pixelColors.Count(c =>
				{
					return c.Hue >= 20 && c.Hue <= 30;
				});
				var percentage = ((decimal)matchCount) / pixelColors.Count;

				if (percentage >= .7m)
				{
					SaveImagePattern(imageId, patternDefinitionId, percentage);
				}
			});
		}

		#endregion "Themes"

		#region "Landscapes"
		/// <summary>
		/// Evaluate the pixels for the "Mountains" pattern (Not Implemented)
		/// </summary>
		/// <param name="imageId">The identifier for the image</param>
		/// <param name="patternDefinitionId">The identifier for the pattern</param>
		/// <param name="pixelColors">The list of pixels from the image</param>
		/// <returns>An asynchronous task to be completed on a different thread</returns>
		private static async Task Pattern_Mountains(long imageId, long patternDefinitionId, List<PixelColor> pixelColors)
		{
			await Task.Run(() => { });
		}
		/// <summary>
		/// Evaluate the pixels for the "Beaches" pattern
		/// </summary>
		/// <param name="imageId">The identifier for the image</param>
		/// <param name="patternDefinitionId">The identifier for the pattern</param>
		/// <param name="pixelColors">The list of pixels from the image</param>
		/// <returns>An asynchronous task to be completed on a different thread</returns>
		private static async Task Pattern_Beaches(long imageId, long patternDefinitionId, List<PixelColor> pixelColors)
		{
			await Task.Run(() =>
			{
				var bluesCount = pixelColors.Where(c => c.Hue.Between(154, 266) && c.Brightness.Between(30, 75) && c.Saturation.Between(20, 80)).Count();
				var tansCount = pixelColors.Where(c => c.Hue.Between(30, 70) && c.Brightness.Between(55, 95) && c.Saturation.Between(10, 35)).Count();

				var count = pixelColors.Count;
				decimal bluesCount_Percent = ((decimal)bluesCount) / count;
				decimal tansCount_Percent = ((decimal)tansCount) / count;

				if (tansCount_Percent >= .2m && bluesCount_Percent >= .3m && (tansCount_Percent + bluesCount_Percent) >= .6m)
				{
					SaveImagePattern(imageId, patternDefinitionId, bluesCount_Percent + bluesCount_Percent);
				}
			});
		}
		/// <summary>
		/// Evaluate the pixels for the "Snow" pattern
		/// </summary>
		/// <param name="imageId">The identifier for the image</param>
		/// <param name="patternDefinitionId">The identifier for the pattern</param>
		/// <param name="pixelColors">The list of pixels from the image</param>
		/// <returns>An asynchronous task to be completed on a different thread</returns>
		private static async Task Pattern_Snow(long imageId, long patternDefinitionId, List<PixelColor> pixelColors)
		{
			await Task.Run(() =>
			{
				var matchCount = pixelColors.Count(c => { return c.Brightness >= 70 && (c.Saturation <= 15 || (c.Saturation <= 35 && c.Hue.Between(170, 260))); });
				decimal percentage = ((decimal)matchCount) / pixelColors.Count;
				if (percentage >= .5m)
				{
					SaveImagePattern(imageId, patternDefinitionId, percentage);
				}
			});
		}
		/// <summary>
		/// Evaluate the pixels for the "Forest" pattern
		/// </summary>
		/// <param name="imageId">The identifier for the image</param>
		/// <param name="patternDefinitionId">The identifier for the pattern</param>
		/// <param name="pixelColors">The list of pixels from the image</param>
		/// <returns>An asynchronous task to be completed on a different thread</returns>
		private static async Task Pattern_Forest(long imageId, long patternDefinitionId, List<PixelColor> pixelColors)
		{
			await Task.Run(() =>
			{
				var greenCount = pixelColors.Count(c => { return c.Brightness.Between(21, 85) && c.Hue.Between(73, 126); });
				var brownCount = pixelColors.Count(c => { return c.Brightness.Between(0, 20) && c.Hue.Between(40, 126); });

				var count = pixelColors.Count;
				decimal green_Percent = ((decimal)greenCount) / count;
				decimal brown_Percent = ((decimal)brownCount) / count;

				if (green_Percent >= .1m && brown_Percent >= .3m && (green_Percent + brown_Percent) >= .6m)
				{
					SaveImagePattern(imageId, patternDefinitionId, green_Percent + brown_Percent);
				}
			});
		}
		/// <summary>
		/// Evaluate the pixels for the "Sunset" pattern
		/// </summary>
		/// <param name="imageId">The identifier for the image</param>
		/// <param name="patternDefinitionId">The identifier for the pattern</param>
		/// <param name="pixelColors">The list of pixels from the image</param>
		/// <returns>An asynchronous task to be completed on a different thread</returns>
		private static async Task Pattern_Sunset(long imageId, long patternDefinitionId, List<PixelColor> pixelColors)
		{
			await Task.Run(() =>
			{
				var bluesAndPurples = pixelColors.Where(c => c.Hue.Between(207, 280) && c.Brightness.Between(0, 45) && c.Saturation.Between(10, 90));
				var redsAndOranges = pixelColors.Where(c => (c.Hue.Between(0, 45) || c.Hue.Between(336, 360)) && c.Brightness.Between(0, 45) && c.Saturation.Between(87, 100));
				var yellows = pixelColors.Where(c => c.Hue.Between(40, 70) && c.Brightness.Between(50, 95) && c.Saturation.Between(90, 100));

				var count = pixelColors.Count;

				decimal bluesAndPurples_Percent = ((decimal)bluesAndPurples.Count()) / count;
				decimal redsAndOranges_Percent = ((decimal)redsAndOranges.Count()) / count;
				decimal yellows_Percent = ((decimal)yellows.Count()) / count;

				if (redsAndOranges_Percent >= .2m && (redsAndOranges_Percent + yellows_Percent + bluesAndPurples_Percent) >= .4m)
				{
					SaveImagePattern(imageId, patternDefinitionId, bluesAndPurples_Percent + redsAndOranges_Percent + yellows_Percent);
				}
			});
		}

		#endregion "Landscapes"
	}
}
